package cn.edu.csust.coursecard.utils.storage;

import cn.edu.csust.coursecard.common.CodeEnum;
import cn.edu.csust.coursecard.common.RedisKeyEnum;
import cn.edu.csust.coursecard.config.OSSConfig;
import cn.edu.csust.coursecard.exception.BaseException;
import cn.edu.csust.coursecard.vo.DogeAPIData;
import cn.edu.csust.coursecard.vo.DogeAPITokenParam;
import cn.edu.csust.coursecard.vo.DogeAPITokenResponse;
import cn.edu.csust.coursecard.vo.DogeCredentials;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Map;

@Slf4j
@Component
@CacheConfig(cacheNames = RedisKeyEnum.S3_NAME)
//@EnableConfigurationProperties(OSSConfig.class)
public class DogeCloudAPI {
    @Resource
    private OSSConfig ossConfig;

    private DogeCredentials credentials;

    private Integer expireAt;

    public boolean isKeyExpired() {
        if (this.credentials != null) {
            long nowTime = new Date().getTime() / 1000;
            // 未过期
            return expireAt.longValue() < nowTime;
        }
        return true;
    }

    public DogeCredentials getServerCredentials() {
        // 判断是否已经获取credentials并且未过期
        boolean expired = isKeyExpired();
        if (!expired) {
            return this.credentials;
        }
        // credentials==null或已过期，直接获取
        DogeAPIData data = this.newTmpToken();
        this.credentials = data.getCredentials();
        this.expireAt = data.getExpiredAt();
        return this.credentials;
    }

    private DogeAPIData newTmpToken() {
        // 获取新的credentials
        DogeAPITokenParam params = new DogeAPITokenParam();
        params.setChannel("OSS_FULL");
        params.setScopes("*");
        DogeAPITokenResponse apiResponse = dogeAPIGet("/auth/tmp_token.json", params);
        if (apiResponse.getCode() != 200) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR, "获取上传密钥错误，多次出错请联系管理员");
        }
        return apiResponse.getData();
    }

    @Cacheable(key = "#{userId}")
    public DogeCredentials getClientCredentials(Integer userId, String scopes) {
        DogeAPITokenParam params = new DogeAPITokenParam();
        params.setChannel("OSS_UPLOAD");
        params.setScopes(scopes);
        DogeAPITokenResponse apiResponse = dogeAPIGet("/auth/tmp_token.json", params);
        return apiResponse.getData().getCredentials();
    }

    // 普通 API 请使用这个方法
    public DogeAPITokenResponse dogeAPIGet(String apiPath, Map<String, String> params) {
        StringBuilder sb = new StringBuilder();
        try {
            for (Map.Entry<String, String> hm : params.entrySet()) {
                sb.append(URLEncoder.encode(hm.getKey(), StandardCharsets.UTF_8.toString())).append('=').append(URLEncoder.encode(hm.getValue(), StandardCharsets.UTF_8.toString())).append("&");
            }
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage());
            return new DogeAPITokenResponse();
        }
        String bodyText = sb.toString().replace("&$", "");
        try {
            return dogeAPIGet(apiPath, bodyText, false);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    // 要求请求内容 Body 是一个 JSON 的 API，请使用这个方法
    public DogeAPITokenResponse dogeAPIGet(String apiPath, DogeAPITokenParam params) {
        try {
            String bodyJson = new ObjectMapper().writeValueAsString(params);
            return dogeAPIGet(apiPath, bodyJson, true);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    // 无参数 API
    public DogeAPITokenResponse dogeAPIGet(String apiPath) {
        try {
            return dogeAPIGet(apiPath, "", true);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    public DogeAPITokenResponse dogeAPIGet(String apiPath, String paramsText, Boolean jsonMode) throws IOException {

        String signStr = apiPath + "\n" + paramsText;
        String sign = "";
        try {
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(new SecretKeySpec(ossConfig.getSecretKey().getBytes(), "HmacSHA1"));
            sign = new String(new Hex().encode(mac.doFinal(signStr.getBytes())), StandardCharsets.UTF_8); // 这里 Hex 来自 org.apache.commons.codec.binary.Hex
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
        String authorization = "TOKEN " + ossConfig.getAccessKey() + ':' + sign;

        URL u = new URL("https://api.dogecloud.com" + apiPath);
        HttpURLConnection conn = (HttpURLConnection) u.openConnection();
        conn.setDoOutput(true);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", jsonMode ? "application/json" : "application/x-www-form-urlencoded");
        conn.setRequestProperty("Authorization", authorization);
        conn.setRequestProperty("Content-Length", String.valueOf(paramsText.length()));
        OutputStream os = conn.getOutputStream();
        os.write(paramsText.getBytes());
        os.flush();
        os.close();
        StringBuilder retJSON = new StringBuilder();
        if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
            String readLine = "";
            try (BufferedReader responseReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
                while ((readLine = responseReader.readLine()) != null) {
                    retJSON.append(readLine).append("\n");
                }
                responseReader.close();
            }
            ObjectMapper mapper = new ObjectMapper();
            DogeAPITokenResponse resp = mapper.readValue(retJSON.toString(), DogeAPITokenResponse.class);
            if (resp.getCode() != 200) {
                System.err.println("{\"error\":\"API 返回错误：" + resp.getMsg() + "\"}");
            } else {
                return resp;
            }
        } else {
            System.err.println("{\"error\":\"网络错误：" + conn.getResponseCode() + "\"}");
        }
        return null;
    }
}
